package cn.xyfmzmy.mybatis;

import java.lang.reflect.*;
import java.sql.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 自定义SqlSessionFactory
 * 利用动态代理,根据传入的Mapper,生成其实现类
 */
public class MySqlSessionFactory {

    private static final String JDBCURL = "jdbc:mysql://localhost:3306/demo";
    private static final String DBUSER = "root";
    private static final String PASSWORD = "123456789";

    /**
     * 传入mapper的class对象，返回实现类
     * @param mapperClass
     * @return
     * @param <T>
     */
    @SuppressWarnings("all")       // 压制警告
    public <T> T getMapper(Class<T> mapperClass){
        // JDK动态代理，创建代理对象，第一个传类加载器，第二个传代理对象要实现接口的class数组
        // 第三个传InvocationHandler的实现类 (类似之前的MyHandler,用于定义接口抽象方法的实现)
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass},
                new MapperInvocationHandler());
    }

    // InvocationHandler的自定义实现
    static class MapperInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // proxy: 创建的代理对象  method:执行方法对象  args:执行方法参数对象  返回的Object是执行方法的返回对象
            if(method.getName().startsWith("select")){
                // 查询数据库，返回示例对象
                return invokeSelect(proxy,method,args);
            }
            return null;
        }

        // jdbc执行select查询
        private Object invokeSelect(Object proxy,Method method,Object[] args){
            // 创建sql语句
            String sql = createSelectSql(method);
            System.out.println(sql);
            try (Connection conn = DriverManager.getConnection(JDBCURL,DBUSER,PASSWORD);
                 PreparedStatement statement = conn.prepareStatement(sql)) {
                // 给 ？赋值
                for(int i=0;i<args.length;i++){
                    Object arg = args[i];
                    if (arg instanceof Integer) {
                        statement.setInt(i+1,(int) arg);
                    }else if (arg instanceof String){
                        statement.setString(i+1,arg.toString());
                    }
                }
                ResultSet resultSet = statement.executeQuery();
                if(resultSet.next()){
                    // 动态解析sql返回结果
                    return parseResult(resultSet,method.getReturnType());
                }
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
            return null;
        }

        // 利用反射机制,根据sql返回结果生成实体对象
        private Object parseResult(ResultSet resultSet, Class<?> returnType) throws Exception {
            Constructor<?> constructor = returnType.getConstructor();       // 获取返回类的构造方法
            Object result = constructor.newInstance();                      // 使用构造方法创建对象,这里是User
            Field[] declaredFields = returnType.getDeclaredFields();        // 获取返回类的属性
            for (Field declaredField : declaredFields) {
                Object column = null;
                String name = declaredField.getName();
                if(declaredField.getType() == Integer.class){
                    column = resultSet.getInt(name);
                }else if(declaredField.getType() == String.class){
                    column = resultSet.getString(name);
                }
                declaredField.setAccessible(true);
                declaredField.set(result,column);            // 给result的相应属性赋值为column
            }
            return result;
        }

        // 利用反射创建sql语句
        private String createSelectSql(Method method) {
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("select ");
            // 获取查询的列
            List<String> selectCols = getSelectCols(method.getReturnType());
            sqlBuilder.append(String.join(",",selectCols));
            sqlBuilder.append(" from ");
            // 获取表名
            String tableName = getSelectTableName(method.getReturnType());
            sqlBuilder.append(tableName);
            // 获取where条件
            sqlBuilder.append(" where ");
            String where = getSelectWhere(method);
            sqlBuilder.append(where);
            return sqlBuilder.toString();
        }

        // 利用反射机制,根据方法信息拼接where条件 (自定义Param注解)
        private String getSelectWhere(Method method) {
            Parameter[] parameters = method.getParameters();            // 获取参数信息
            return Arrays.stream(parameters).map((parameter)->{
                Param param = parameter.getAnnotation(Param.class);     // 获取注解信息
                String column = param.value();
                return column + " = ?";
            }).collect(Collectors.joining(" and "));            // 收集的时候添加分割符
        }

        // 利用反射机制,根据返回值类型获取表名 (自定义Table注解)
        private String getSelectTableName(Class<?> returnType) {
            Table table = returnType.getAnnotation(Table.class);        // 获取注解
            if(table == null){
                throw new RuntimeException("返回值无法确定查询表");
            }
            return table.tableName();
        }

        // 利用反射机制，根据返回值类型获取需要查询的列
        private List<String> getSelectCols(Class<?> returnType){
            Field[] declaredFields = returnType.getDeclaredFields();    // 获取属性
            return Arrays.stream(declaredFields).map(Field::getName).toList();
        }
    }


}
